package com.mars.framework.aspect;

import com.mars.common.util.StringUtil;
import com.mars.common.util.ip.IpUtils;
import com.mars.framework.annotation.RateLimiter;
import com.mars.framework.exception.ServiceException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author 程序员Mars
 */
@Component
@Aspect
public class RateLimiterAspect extends BaseSpELAspect {


    @Resource
    public RedisTemplate<String, String> redisTemplate;

    @Pointcut("@annotation(com.mars.framework.annotation.RateLimiter)")
    public void rateLimiterPointCut() {
    }

    /**
     * 环绕通知，用于在方法执行前进行限流判断
     */
    @Around("rateLimiterPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String[] parameterNames = (new LocalVariableTableParameterNameDiscoverer())
                .getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
        Object[] args = joinPoint.getArgs();
        //获取自定义注解
        RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class);
        int millis = rateLimiter.expireTime();
        String lockKey = rateLimiter.key();
        String key = super.getValueBySpEL(lockKey, parameterNames, args, "RepeatSubmit").get(0);
        //获取当前时间戳
        long currentTimeMillis = System.currentTimeMillis();
        String lastTimeMillis = redisTemplate.opsForValue().get(key);
        if (StringUtil.isNotEmpty(lastTimeMillis)) {
            //并且当前时间与上一次访问时间的差小于限流时间间隔，则抛出异常，否则更新`ratelimitermap`中的时间戳。
            if ((currentTimeMillis - Long.parseLong(Objects.requireNonNull(lastTimeMillis))) < millis) {
                throw new ServiceException("请求过于频繁，请稍后再试");
            }
        }
        redisTemplate.opsForValue().setIfAbsent(key, currentTimeMillis + "", millis, TimeUnit.MILLISECONDS);
        return joinPoint.proceed();
    }

}
